﻿using System;
using System.Collections.Generic;
using System.Linq;
using SymbolIntegration.core.Expressions.Functions;
using SymbolIntegration.core.Helpers;

namespace SymbolIntegration.core.Expressions
{
    public class PowerExpression : Expression
    {
        private readonly Expression m_Base;

        public Expression Power
        {
            get { return m_Power; }
        }

        public Expression Base
        {
            get { return m_Base; }
        }

        private readonly Expression m_Power;

        internal static PowerExpression ParseItself(string expression, int position, int size)
        {
            int oldPosition = position;
            int oldSize = size;
            ParseUtils.TrimScopes(expression, ref position, ref size);
            PowerExpression result = null;
            if (size == 0)
            {
                throw new ParseException("Expression is scopes or spaces. Need power", expression, oldPosition, oldSize,
                    0);
            }
            int endPosition = position + size - 1;

            for (int i = position; i <= endPosition; i++)
            {
                if (expression[i] == '(')
                {
                    i = ParseUtils.FindCloseScopePosition(expression, i, size + position - i);
                }
                if (expression[i] == '^')
                {
                    var baseExpr = ParseUtils.Parse(expression, position, i - position);
                    var powerExpr = ParseUtils.Parse(expression, i + 1, endPosition - i);
                    result = new PowerExpression(baseExpr, powerExpr);
                    break;
                }
            }

            return result;
        }

        public PowerExpression(Expression baseExpression, Expression powExpression)
        {
            m_Base = baseExpression;
            m_Power = powExpression;
        }

        public override ExpressionType Type
        {
            get { return ExpressionType.Power; }
        }

        public override Expression Clone()
        {
            var clone = new PowerExpression (m_Base, m_Power);
            InnerCopy(clone);
            return clone;
        }

        public override Expression Substitute(Dictionary<string, Expression> varValues)
        {
            var clone = new PowerExpression(m_Base.Substitute(varValues), m_Power.Substitute(varValues));
            InnerCopy(clone);
            return clone;
        }

        public override Expression Differentiate(string variable)
        {
            Expression result;
            do
            {
                if (!IsDependsFrom(variable))
                {
                    result = new ConstantExpression(0);
                    break;
                }

                var constant = m_Power as ConstantExpression;
                if (constant != null)
                {
                    double value = constant.Value;
                    if (MathHelper.AlmostEqual(value, 0))
                    {
                        result = new ConstantExpression(0);
                        break;
                    }

                    if (MathHelper.AlmostEqual(value, 1))
                    {
                        result = m_Base.Differentiate(variable);
                        break;
                    }

                    var newPowExpr = new PowerExpression(m_Base, new ConstantExpression(value - 1));
                    result = new ConstantExpression(value) * newPowExpr * m_Base.Differentiate(variable);
                    break;
                }

                constant = m_Base as ConstantExpression;
                if (constant != null)
                {
                    result = new LnFunction(constant) * this * m_Power.Differentiate(variable);
                    break;
                }
                //dx(a^b) = a^b * dx(b * ln(a))
                var multInner = m_Power * new LnFunction(m_Base);
                result = this * multInner.Differentiate(variable);
            } while (false);

            result.InvertNegate(IsNegate);
            return result;
        }

        public override Expression Normalize()
        {
            Expression result;
            do
            {
                var normalPower = m_Power.Normalize();
                var normalBase = m_Base.Normalize();
                var powerExprBase = normalBase as PowerExpression;
                if (powerExprBase != null)
                {
                    var newPowerBase = new PowerExpression(powerExprBase.m_Base, powerExprBase.m_Power * normalPower);
                    result = newPowerBase.InvertNegate(IsNegate).Normalize();
                    break;
                }

                var expBaseExpr = normalBase as ExpFunction;
                if (expBaseExpr != null)
                {
                    var newPowerBase = new ExpFunction(expBaseExpr.Argument * m_Power);
                    result = newPowerBase.InvertNegate(IsNegate).Normalize();
                    break;
                }

                var constantPower = normalPower as ConstantExpression;
                var constantBase = normalBase as ConstantExpression;
                //Check in case of constants
                if (constantPower != null)
                {
                    if (constantBase != null)
                    {
                        double value = Math.Pow(constantBase.Value, constantPower.Value);
                        value = IsNegate ? -value : value;
                        result = new ConstantExpression(value);
                        break;
                    }

                    if (MathHelper.AlmostEqual(constantPower.Value, 0))
                    {
                        double value = IsNegate ? -1 : 1;
                        result = new ConstantExpression(value);
                        break;
                    }

                    if (MathHelper.AlmostEqual(constantPower.Value, 1))
                    {
                        result = normalBase.InvertNegate(IsNegate).Normalize();
                        break;
                    }

                    //Special case for -1
                    if (MathHelper.AlmostEqual(constantPower.Value, -1))
                    {

                        result = PowerDistributed(normalBase.SetNegate(false), new ConstantExpression(-1));
                        result = result.InvertNegate(IsNegate ^ normalBase.IsNegate);
                        break;
                    }

                    //If complex type
                    if (MathHelper.IsInteger(constantPower.Value) && 
                        (normalBase.Type == ExpressionType.Addends || normalBase.Type == ExpressionType.Multipliers ||
                        normalBase.Type == ExpressionType.Divide || 
                        normalBase is SinFunction || normalBase is CosFunction))
                    {
                        var multipliers = normalBase*normalBase;
                        for (int i = 2; i < Math.Abs(constantPower.Value) - 0.5; i++)
                        {
                            multipliers = multipliers*normalBase;
                        }
                        var normalMultipliers = multipliers.Normalize();
                        if (constantPower.Value < 0)
                        {
                            result = PowerDistributed(normalMultipliers, new ConstantExpression(-1));
                            result = result.InvertNegate(IsNegate);
                        }
                        else
                        {
                            result = normalMultipliers.InvertNegate(IsNegate);
                        }
                        break;
                    }
                }

                result = PowerDistributed(normalBase, normalPower);
                result.InvertNegate(IsNegate);
                
            } while (false);

            if (result.Type == ExpressionType.Multipliers)
            {
                result = result.Normalize();
            }

            return result;
        }

        private static Expression PowerDistributed(Expression baseExpression, Expression powerExpression)
        {
            Expression result;
            do
            {
                var multipliers = baseExpression as MultipliersExpression;
                if (multipliers == null)
                {
                    result = baseExpression ^ powerExpression;
                    break;
                }

                var multipliersList = multipliers.Multipliers.Select(expression =>
                    (expression ^ powerExpression).Normalize()).ToList();
                multipliersList.Add( (multipliers.Constant ^ powerExpression ).Normalize());
                result = multipliersList[0];
                for (int i = 1; i < multipliersList.Count; i++)
                {
                    result = result*multipliersList[i];
                }
            } 
            while (false);

            return result;
        }

        public override bool IsEqual(Expression right)
        {
            bool result = false;
            do
            {
                if (right.IsNegate != IsNegate)
                {
                    break;
                }

                var power = right as PowerExpression;
                if (power == null)
                {
                    break;
                }

                if (!m_Base.IsEqual(power.m_Base))
                {
                    break;
                }

                if (!m_Power.IsEqual(power.m_Power))
                {
                    break;
                }

                result = true;
            }
            while (false);

            return result;
        }

        public override Polinom FormatToPolinomAsDependentFromVariable(string variable)
        {
            Polinom polinom = null;
            do
            {
                var constantPower = m_Power as ConstantExpression;
                var varBase = m_Base as VariableExpression;
                if (constantPower != null && varBase != null && MathHelper.IsInteger(constantPower.Value))
                {
                    int power = MathHelper.GetAsInteger(constantPower.Value);
                    if (power > 0)
                    {
                        polinom = new Polinom(variable);
                        polinom.SetConstantAt(power, new ConstantExpression(1).SetNegate(IsNegate).Normalize());
                        break;
                    }
                }
            } 
            while (false);

            return polinom;
        }

        public override string ToString()
        {
            string result = "";
            var constantPower = m_Power as ConstantExpression;
            if (constantPower != null && MathHelper.AlmostEqual(constantPower.Value, -1))
            {
                result = "1/";
                if (m_Base.Type == ExpressionType.Addends || m_Base.Type == ExpressionType.Multipliers ||
                m_Base.Type == ExpressionType.Power)
                {
                    result += "(" + m_Base + ")";
                }
                else
                {
                    result += m_Base;
                }
                if (IsNegate)
                {
                    result = "-" + result;
                }
                return result;
            }

            if (m_Base.Type == ExpressionType.Addends || m_Base.Type == ExpressionType.Multipliers ||
                m_Base.Type == ExpressionType.Power)
            {
                result += "(" + m_Base + ")";
            }
            else
            {
                result += m_Base;
            }
            result += "^";
            if (m_Power.Type == ExpressionType.Addends || m_Power.Type == ExpressionType.Multipliers ||
                m_Power.Type == ExpressionType.Power)
            {
                result += "(" + m_Power + ")";
            }
            else
            {
                result += m_Power;
            }
            if (IsNegate)
            {
                result = "-" + result;
            }
            return result;
        }

        protected internal override void RebuildDependancyCache()
        {
            m_Cache.Varibles.Clear();
            if (!m_Power.m_Cache.IsValid)
            {
                m_Power.RebuildDependancyCache();
            }
            m_Cache.Varibles.UnionWith(m_Power.m_Cache.Varibles);

            if (!m_Base.m_Cache.IsValid)
            {
                m_Base.RebuildDependancyCache();
            }
            m_Cache.Varibles.UnionWith(m_Base.m_Cache.Varibles);

            m_Cache.IsValid = true;
        }

        protected internal override void FillExpressionStatistic(ExpressionStatistic statistic, string integrateVariable)
        {
            m_Base.FillExpressionStatistic(statistic, integrateVariable);
            m_Power.FillExpressionStatistic(statistic, integrateVariable);
            if (FormatToPolinom(integrateVariable) == null)
            {
                statistic.HasUnpolinomicPowers = true;
            }
        }

        protected internal override Expression IntegrateAsDependentFromVar(string variable, IIntegrator integrator)
        {
            Expression result;
            do
            {
                var polinomPower = m_Power.FormatToPolinom(variable);
                var polinomBase = m_Base.FormatToPolinom(variable);
                if (polinomPower != null && polinomBase != null)
                {
                    if (polinomBase.GetMaxPower() == 1 && polinomPower.GetMaxPower() == 0)
                    {
                        var constantPower = polinomPower.GetConstantAt(0) as ConstantExpression;
                        if (constantPower != null && MathHelper.AlmostEqual(constantPower.Value, -1))
                        {
                            result = new LnFunction(m_Base) / polinomBase.GetConstantAt(1);
                        }
                        else
                        {
                            var constantBefore = m_Power + new ConstantExpression(1);
                            result = new PowerExpression(m_Base, constantBefore)
                                / polinomBase.GetConstantAt(1) / constantBefore;
                        }
                        break;
                    }

                    if (polinomBase.GetMaxPower() == 0 && polinomPower.GetMaxPower() == 1)
                    {
                        result = new LnFunction(m_Base) * new PowerExpression(m_Base, m_Power) / polinomPower.GetConstantAt(1);
                        break;
                    }
                }

                result = integrator.TryIntegrateMultipliers(new List<Expression> { SetNegate(false) }, variable);
            } 
            while (false);

            result = result.InvertNegate(IsNegate);
            return result;
        }

        public static List<Expression> MergeMul(List<Expression> multsList)
        {
            bool bIsAllNegate = false;
            var allPowerMulList = new List<PowerExpression>();
            foreach (var expression in multsList)
            {
                bIsAllNegate = expression.IsNegate ^ bIsAllNegate;
                var powerExpression = expression as PowerExpression;
                if (powerExpression == null)
                {
                    var functExpression = expression as ExpFunction;
                    if (functExpression == null)
                    {
                        powerExpression = new PowerExpression(expression.SetNegate(false), new ConstantExpression(1));
                    }
                    else
                    {
                        powerExpression = new PowerExpression(new ExpFunction(new ConstantExpression(1)),
                            functExpression.Argument);
                    }
                }

                allPowerMulList.Add(powerExpression);
            }

            var mergedPowerList = new List<PowerExpression>();
            foreach (var powerExpression in allPowerMulList)
            {
                bool bIsMerged = false;
                for (int i = 0; i < mergedPowerList.Count; i++)
                {
                    if (mergedPowerList[i].m_Base.IsEqual(powerExpression.m_Base))
                    {
                        var mergedPower = AddendsExpression.Add(powerExpression.m_Power, mergedPowerList[i].m_Power);
                        var mergedPowerPossibleConstant = mergedPower.TrySimplifyByConstant() ?? mergedPower;
                        mergedPowerList[i] = new PowerExpression(powerExpression.m_Base, mergedPowerPossibleConstant);
                        bIsMerged = true;
                    }
                }
                if (!bIsMerged)
                {
                    mergedPowerList.Add(powerExpression);
                }
            }
            var result =
                mergedPowerList.Select(expression => expression.TrySimplifyByConstant() ?? expression).ToList();
            if (bIsAllNegate)
            {
                result.Add(new ConstantExpression(-1));
            }
            return result;
        }

        private Expression TrySimplifyByConstant()
        {
            Expression result = null;
            var constantPower = m_Power as ConstantExpression;
            var constantBase = m_Base as ConstantExpression;
            //Check in case of constants
            if (constantPower != null)
            {
                if (constantBase != null)
                {
                    double value = Math.Pow(constantBase.Value, constantPower.Value);
                    value = IsNegate ? -value : value;
                    result = new ConstantExpression(value);
                }
                else if (MathHelper.AlmostEqual(constantPower.Value, 0))
                {
                    double value = IsNegate ? -1 : 1;
                    result = new ConstantExpression(value);
                }
                else if (MathHelper.AlmostEqual(constantPower.Value, 1))
                {
                    result = m_Base.InvertNegate(IsNegate).Normalize();
                }
            }
            //Check in case of exp(1)^expr
            else
            {
                var exp = m_Base as ExpFunction;
                if (exp != null && exp.IsEqual(new ExpFunction(new ConstantExpression(1))))
                {
                    result = new ExpFunction(m_Power).SetNegate(IsNegate);
                }
            }


            return result;
        }
    }
}
